﻿
--[[    
    Paranoia Enemy Player Alert
    Copyright (C) 2008 Victor Barrancos
  
    This file is part of Paranoia Enemy Player Alert.

    Paranoia Enemy Player Alert is free software: you can redistribute it 
    and/or modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation, either version 3 of the 
    License, or (at your option) any later version.

    Paranoia Enemy Player Alert is distributed in the hope that it will 
    be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 
    of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
    General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Paranoia Enemy Player Alert.  If not, see 
    <http://www.gnu.org/licenses/>.

--===================================================================================================================--
  Addon Name:   Paranoia Enemy Player Alert
  Version:      1.13
  Authors:      Original (v1.0-v1.03): 
                  rmet0815      
                2.4 Update (v1.04) and beyond: 
                  Lifetapt
  Contact:      Email: 
                  lifetapt@gmail.com
                In-game:
                  Lifetapt @ Alterac Mountains
                Curse/WoWI/IncGamers:
                  Lifetapt
--===================================================================================================================--

This file is the Paranoia core, it includes hostile handling, some gui stuff, cl parsing and detection, utility functions,
database functions, slash handling, announcements, the enemy list, the minimap button, addon communication, and more.

Paranoia has grown *way* past anything I had expected, and I imagine the original author too. As a result the code is
not well organized and very cluttered. As I add more stuff I'll be splitting this file up, as I have done with the browser.
]]--

Paranoia = {};
Paranoia_updater = CreateFrame("Frame", "ParanoiaUpdater"); --basically just calls onupdate
local Dewdrop = AceLibrary("Dewdrop-2.0")

PARANOIA_VERSION = "1.13 beta1";
PARANOIA_COMMVERSION = "1";
PARANOIA_DBVERSION = 1.1;
PARANOIA_REALMNAME = "";

--Constants=========================>
local PARANOIA_HOSTILEPC_UNITFLAG =  0x548;
local PARANOIA_FONTLIST = {"NumberFontNormal", "NumberFontNormalLarge", "NumberFontNormalHuge", "GameFontNormal", "GameFontNormalLarge", "GameFontNormalHuge", "ChatFontNormal", "SystemFont", "MailTextFontNormal", "QuestTitleFont"};

--Warning Variables=================>
local lastWarning = 0;
local activeHostileList = {};
local ignoreList = {};

--Player Database Variables=========>
Paranoia_edb = {};
local edb = {};

--Ping Variables===================>
local isPingWaiting = false;
local gotFirstReply = false;
local lastReplyTime = 0;

--Miscellaneous Variables===========>
local haveVarsBeenLoaded = false;
local isParanoiaDisabled = false;
local isConfigOpen = false;
local fadeStart = 0;
local debugEnabled = false;
local verboseEnabled = false;
local configCopy = {}

--Announce Channel Variables========>
local channelid;
local channelname;

--Inactivity Fade Variables=========>
local timeSinceLast, fadeTicks, stepAlpha, textAlpha, colorR, colorG, colorB;

--Faction Variables=================>
PARANOIA_OPPFACTION = UNKNOWN;
PARANOIA_OPPFACTIONENG = UNKNOWN;

--Detection Variables===============>
local hostileSource = false;
local hostileDest = false;
local knownSkill = false;
local guessedClass = UNKNOWN;
local guessedLevel = 0;
local otherBuff = 0;
local lastDamager = UNKNOWN;
local lastDamagerTime = 0;

--Dialog Boxes======================>
  StaticPopupDialogs["RELOADUIPROMPT"] = {
    text = PARANOIA_RELOADPROMPTTEXT,
    button1 = YES,
    button2 = NO,
    OnAccept = function()
      Paranoia:DoResetFrames();
    end,
    timeout = 0,
    whileDead = 1,
    hideOnEscape = 0,
    showAlert = 1,
  };
  
  StaticPopupDialogs["RELOADUIPROMPT2"] = {
    text = PARANOIA_DEFAULTPROMPTTEXT,
    button1 = YES,
    button2 = NO,
    OnAccept = function()
      Paranoia:DoResetSettings();
    end,
    timeout = 0,
    whileDead = 1,
    hideOnEscape = 0,
    showAlert = 1,
  };
  
  StaticPopupDialogs["PARANOIAHIDDEN"] = {
    text = string.format(PARANOIA_FIRSTRUNPROMPTTEXT, GetZoneText()),
    button1 = YES,
    button2 = NO,
    OnAccept = function()
      Paranoia:ShowConfig();
    end,
    timeout = 0,
    whileDead = 1,
    hideOnEscape = 0,
    showAlert = 1,
  };
  
  StaticPopupDialogs["PARANOIAKILLED"] = {
    text = "Paranoia: You were killed by "..lastDamager..". Do you wish to add this player to your KoS list? (Auto-dismissing in 6 seconds)",
    button1 = YES,
    button2 = NO,
    OnAccept = function()
      Paranoia:ShowConfig();
    end,
    timeout = 6,
    whileDead = 1,
    hideOnEscape = 1,
    showAlert = 1,
  };
  
  StaticPopupDialogs["PARANOIAPURGE"] = {
    text = "Paranoia: This realm's database contains over 5000 players. You may want to purge old players from the database to reduce Paranoia's memory usage.",
    button1 = OKAY,
    timeout = 10,
    whileDead = 1,
    hideOnEscape = 1,
    showAlert = 1,
  };
  
  --Default Settings==================>
  Paranoia_Config = { enabled = true,
    disableInBattlefield = true,
    disableInArena = true,
    disableInSanctuaries = true,
    disableInFFA = true,
    hideList = false,
    listLocked = false,
    enableSound = true,
    inactivityFade = true,
    borderOpacity = 1,
    frameOpacity = 1,
    alertPopupEnabled = true,
    alertPopupfadeTime = 2,
    alertPopupdisplayTime = 3, 
    alertPopupAlpha = 1,
    listTimeout = 60,
    warnTimeout = 60,
    maxHostiles = 15,
    customMsg = PARANOIA_DEFAULTMSG,
    listScale = 1.0,
    enableComm = true,
    partyWarn = true,
    guildWarn = true,
    minimapPos = 172,
    hideMinimap = false,
  };

--===================================================================================================================--
--  2) Event Handlers
--===================================================================================================================--

function Paranoia:OnLoad(self)
  SLASH_PARANOIACONF1 = PARANOIA_SLASH1;
  SLASH_PARANOIACONF2 = PARANOIA_SLASH2;
  SlashCmdList["PARANOIACONF"] = function(msg) Paranoia:SlashHandler(msg); end
  SLASH_PARANOIARL1 = "/rl";
  SlashCmdList["PARANOIARL"] = function(msg) ReloadUI(); end

  self:RegisterForDrag("LeftButton");
  
  self:RegisterEvent("PLAYER_ENTERING_WORLD");
  self:RegisterEvent("VARIABLES_LOADED");
  self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED"); -- main detection method, fires any time there's an incoming combat log event.
  self:RegisterEvent("PARTY_MEMBERS_CHANGED");
  self:RegisterEvent("ZONE_CHANGED");
  self:RegisterEvent("ZONE_CHANGED_NEW_AREA");
  self:RegisterEvent("PLAYER_REGEN_DISABLED");       -- entered combat
  self:RegisterEvent("PLAYER_REGEN_ENABLED");        -- left combat
  self:RegisterEvent("UPDATE_MOUSEOVER_UNIT");       -- when we mouseover something
  self:RegisterEvent("PLAYER_TARGET_CHANGED");       -- when we switch targets
  self:RegisterEvent("CHAT_MSG_ADDON");              -- pings/usercommun
  self:RegisterEvent("PLAYER_DEAD");
  self:RegisterEvent("DUEL_REQUESTED")               -- used to ignore the player we are dueling

  --Initialize button positions
  local i = 2;
  local thisItem;
  
  Paranoia_StatusFrame1:ClearAllPoints(); --Anchors the first button to the enemy list.
  Paranoia_StatusFrame1:SetPoint("TOPLEFT", "Paranoia_Main", "TOPLEFT", 8, -8);
  
  while (i <= 15) do --This loops through buttons 2-15 and anchors them to the button preceding it.
    thisItem = getglobal("Paranoia_StatusFrame"..i);
    thisItem:ClearAllPoints();
    thisItem:SetPoint("TOPLEFT", "Paranoia_StatusFrame"..(i - 1), "BOTTOMLEFT", 0, 0);
    i = i + 1;
  end
  
end

-- 1352 is the unitflag we want, (hexadecimal 0x548)
-- we want to search in arg5 and arg8 (sourceFlags and destFlags)
-- 4424 = hostile pet

function Paranoia:OnEvent(event)
  if (event == "PLAYER_ENTERING_WORLD") then
    if not (Paranoia_Main:IsUserPlaced()) or not (Paranoia_Warn:IsUserPlaced()) then 
      if Paranoia:IsPlayerInSanctuary() or Paranoia:IsPlayerInBattlefield() or Paranoia:IsPlayerInFFA() then
        Paranoia:Msg(string.format(PARANOIA_CHATFIRSTRUNHIDDEN, GetZoneText()));
        StaticPopup_Show ("PARANOIAHIDDEN");
      else
        Paranoia:Msg(PARANOIA_CHATFIRSTRUN); 
      end
      Paranoia_Main:SetUserPlaced(true);
      Paranoia_Warn:SetUserPlaced(true);
    end
    if Paranoia:IsPlayerInInstance() then 
      Paranoia:hideList(true); 
      Paranoia:Msg(PARANOIA_CHATINSTANCE);
    end
  elseif (event == "VARIABLES_LOADED") then
    Paranoia:VariblesLoaded();
    Paranoia:partyMembersChanged();
    DEFAULT_CHAT_FRAME:AddMessage(string.format("|cFFFF7D0A"..PARANOIA_CHATPARALOADED, PARANOIA_VERSION, "|r|cFF9482CALifetapt|r|cFFFF7D0A"));
    if (GetLocale() ~= "enUS" and GetLocale() ~= "enGB") and PARANOIA_CHATLOCALIZEDBY then
      DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A"..PARANOIA_CHATLOCALIZEDBY);
    end
    if getn(Paranoia_edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG]) > 5000 then StaticPopup_Show("PARANOIAPURGE"); end
  elseif (event == "COMBAT_LOG_EVENT_UNFILTERED") then
    
    if (debugEnabled and verboseEnabled) then Paranoia:dMsg(tostring(arg2).." \n|cFFFFFFFFSourceName:|r "..tostring(arg4).." \n|cFFFFFFFFSourceFlags:|r "..tostring(arg5).." \n|cFFFFFFFFDestName:|r "..tostring(arg7).." \n|cFFFFFFFFDestFlags:|r "..tostring(arg8).." \n|cFFFFFFFFSpell:|r "..tostring(arg9).." ("..tostring(arg10)..")"); end
    --compare sourceFlags (arg5) and destFlags (arg8).
    --if either are 0x548 (hostile) or 0x528 (neutral).
    
    local bit_band = _G.bit.band
    if bit_band(arg5, PARANOIA_HOSTILEPC_UNITFLAG) == PARANOIA_HOSTILEPC_UNITFLAG then
      Paranoia:dMsg("***Source is hostile.");
      hostileSource = true;
    end
    
    if bit_band(arg8, PARANOIA_HOSTILEPC_UNITFLAG) == PARANOIA_HOSTILEPC_UNITFLAG then
      Paranoia:dMsg("***Destination is hostile.");
      hostileDest = true;
    end
    
    --hostile action is true, so now we'll check to see if the event was caused by a spell/skill (as opposed to simply
    --environmental damage or a regular melee swing)
    --we will only check if the event was caused by a spell/skill if the hostile player is in the source flags, to avoid misguesses
    --for example, if the hostile player is a Paladin who just gained arcane intellect, the addon would misclassify them as a mage.
    --however, if otherBuff returns zero, this means the buff cannot be cast on other players, so we can use the destFlags.
      
      if (hostileSource == true) then --The source of the event is hostile.
        if arg7 == UnitName("player") then  --if the destination of the combat log event is the player, then store the attacker's name.
          lastDamagerTime = GetTime();
          lastDamager = arg4
        end
        if Paranoia_SpellList == nil then 
          Paranoia:dMsg("Paranoia's spell list is not in memory; loading spell list.");
          lastWarning = GetTime();
          Paranoia:LoadSpellList();
        end   --The spell list has been unloaded because of inactivity, we need to reload it first.
        if (string.find(arg2, "RANGE_") or string.find(arg2, "SPELL_") or string.find(arg2, "SPELL_PERIODIC_")) then  --Event was not environmental or melee swing
          if (Paranoia_SpellList[arg9] ~= nil) then
            guessedClass = Paranoia_SpellList[arg9].Class; 
            guessedLevel = Paranoia_SpellList[arg9].Level;
            knownSkill = true;
          else
            knownSkill = false; 
            guessedClass = UNKNOWN;
            guessedLevel = 0;
          end
        else  --Event was environmental or melee swing.
          guessedClass = UNKNOWN;
          guessedLevel = 0;
          knownSkill = false;
        end
        Paranoia:warnHostilePlayer(arg2, arg4, guessedClass, guessedLevel, 0);
      end
      
      if (hostileDest == true) then --The destination of the event is hostile (we don't try to guess class/level).
        if arg2 == "PARTY_KILL" and arg4 == UnitName("player") then
          if Paranoia_edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][arg7] ~= nil then
            Paranoia:incrementKill(arg7);
            Paranoia:dMsg("Player has killed "..arg7..". Incrementing kill count.");
          end
        end
        knownSkill = false; 
        guessedClass = UNKNOWN;
        guessedLevel = 0;
        Paranoia:warnHostilePlayer(arg2, arg7, guessedClass, guessedLevel, 0);
      end
      
      hostileSource = false;
      hostileDest = false;
      knownSkill = false;
      guessedClass = UNKNOWN;
      guessedLevel = 0;
      otherBuff = 0;
  elseif (event == "PLAYER_DEAD") then
    if lastDamager ~= UNKNOWN and lastDamager ~= nil then
      if Paranoia_edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][lastDamager] ~= nil then
        Paranoia:incrementDeath(lastDamager);
        StaticPopupDialogs["PARANOIAKILLED"].text = "You were killed by "..lastDamager..". Do you wish to add this player to your KoS list? (Auto-dismissing in 6 seconds)"
        if debugEnabled then StaticPopup_Show("PARANOIAKILLED"); end
      end
    end
  elseif (event == "DUEL_REQUESTED") then
    Paranoia:addNotHostile(arg1, (60*3));
  elseif (event == "PARTY_MEMBERS_CHANGED") then
    Paranoia:partyMembersChanged();
  elseif (event == "ZONE_CHANGED" or event =="ZONE_CHANGED_NEW_AREA") then
    Paranoia:dMsg(event.." event fired!");
    Paranoia:OnUpdate();
  elseif (event == "PLAYER_REGEN_DISABLED") then
    Paranoia_inCombatHeader:SetText("|cFFFF0000"..TUTORIAL_TITLE5.."|r")
  elseif (event == "PLAYER_REGEN_ENABLED") then
    Paranoia_inCombatHeader:SetText("")
  elseif (event == "UPDATE_MOUSEOVER_UNIT") then
    --unitisplayer() returns either true or nil, not false. Unitisfriend returns 1 or nil.
    if UnitIsPlayer("mouseover") ~= nil and UnitIsFriend("player","mouseover") ~= 1 then
      local guildName, _, _ = GetGuildInfo("mouseover");
      Paranoia:dMsg("Mouseover unit is a hostile player character.");
      Paranoia:warnHostilePlayer(event, UnitName("mouseover"), UnitClass("mouseover"), UnitLevel("mouseover"), 1, _, guildName);
    elseif UnitIsPlayer("mouseover") ~= nil then 
      Paranoia:addNotHostile(UnitName("mouseover"), (60*3)); 
    end
  elseif (event == "PLAYER_TARGET_CHANGED") then
    if (UnitIsPlayer("target") ~= nil and UnitIsFriend("player","target") ~= 1) then
      local guildName, _, _ = GetGuildInfo("mouseover");
      Paranoia:dMsg("Targeted unit is a hostile player character.");
      Paranoia:warnHostilePlayer(event, UnitName("target"), UnitClass("target"), UnitLevel("target"), 1, _, guildName);
    elseif UnitIsPlayer("target") ~= nil then 
      Paranoia:addNotHostile(UnitName("target"), (60*3));
    end
  elseif (event == "CHAT_MSG_ADDON") then --Arg1 = prefix, arg2 = msg (delimited by a caret [^]), arg3 = channel, arg4 = sender
    if Paranoia_Config.enableComm == false then return end
    if arg1 == "PEPA"  then
      local commVersion, locale, commandType, msg = string.split("%", arg2)
      if commandType == "DATA" then
        if arg4 == UnitName("player") then return; end
        if commVersion ~= PARANOIA_COMMVERSION then return; end
        local sourcetype = 0;
        
        if arg3 == "GUILD" then sourcetype = 3; end
        if arg3 == "PARTY" then sourcetype = 2; end
        
        local name, class, level = string.split("^", msg);
        
        if sourcetype == 2 and Paranoia_Config.partyWarn == true then
          Paranoia:warnHostilePlayer(event, name, class, level, sourcetype, arg4);
        end
        if sourcetype == 3 and Paranoia_Config.guildWarn == true then 
          if Paranoia:IsGuildieNearby(arg4) then
            Paranoia:warnHostilePlayer(event, name, class, level, sourcetype, arg4);
          end
        end
      elseif commandType == "PING" then
        Paranoia:dMsg("Recieved a PING from "..arg4.."... sending PONG.");
        Paranoia:commMsg("PONG", msg, arg3, "ALERT", arg4);
      elseif commandType == "PONG" then
        if isPingWaiting == false then return; end
        if arg4 == UnitName("player") then return; end
        if gotFirstReply == false then gotFirstReply = true; end
        if msg == UnitName("player") then 
          if locale ~= GetLocale() then Paranoia:Msg(arg4.." is using a different locale ("..locale.."). You may receive class names in a different language.");  end
          if commVersion ~= PARANOIA_COMMVERSION then Paranoia:Msg(arg4.." is using an incompatible version of Paranoia. Data sharing will be disabled for this player."); end
        end
        lastReplyTime = GetTime();
        Paranoia:dMsg("Recieved a PONG from "..arg4.."!")
      end
    end
  end
end

function Paranoia:OnDragStart(self)
  if self:GetName() == "Paranoia_Main" then
    if Paranoia_Config.listLocked then return; end
    UIFrameFadeOut(self, 0.3, Paranoia_Config.frameOpacity, 0.5);
  else
    UIFrameFadeOut(self, 0.3, 1, 0.5);
  end
  self:StartMoving(); 
end

function Paranoia:OnDragStop(self)
  if self:GetName() == "Paranoia_Main" then
    if Paranoia_Config.listLocked then return; end
    if Paranoia:round(self:GetAlpha(), 2) ~= Paranoia_Config.frameOpacity then UIFrameFadeIn(self, 0.3, 0.5, Paranoia_Config.frameOpacity); end
  else
    UIFrameFadeIn(self, 0.3, 0.5, 1);
  end
  self:StopMovingOrSizing();
end

function Paranoia:OnUpdate()
  --OnUpdate is primarily responsible for hiding and showing Paranoia based upon the location of the current player, 
  --as well handling ping timeouts and unloading the SpellList after a minute of inactivity.
  
  if Paranoia_SpellList ~= nil then
    if (GetTime() - lastWarning) > 60 then
      Paranoia:dMsg("There has been no enemy activity in the past 60 seconds; unloading spell list from memory.");
      Paranoia_SpellList = nil;
    end
  end
  
  if isPingWaiting then
    if (GetTime() - lastReplyTime) > 4 then
      isPingWaiting = false;
      if not gotFirstReply then Paranoia:Msg("Couldn't find any player(s) using Paranoia. Why not spread the word? :D"); end
    end
  end
  
  if lastDamager ~= UNKNOWN then
    if (GetTime() - lastDamagerTime) > 8 then
      lastDamagerTime = 0;
      lastDamager = UNKNOWN;
    end
  end
  
  if Paranoia:IsPlayerInInstance() then return; end
  if haveVarsBeenLoaded then
  
    --This closes the frame once the fadeout is finished.
    if (not (fadeStart == 0)) then
      if (GetTime() - fadeStart) > 0.4 then
        Paranoia:dMsg("Closing main frame.");
        fadeStart = 0;
        if Paranoia:round(Paranoia_Main:GetAlpha(), 2) == 0 then Paranoia_Main:Hide(); end
      end
    end
    Paranoia:updateVisibility();
    Paranoia:updateHostileList();
  end
  
end

--===================================================================================================================--
--  3) Initialization Functions
--===================================================================================================================--

function Paranoia:VariblesLoaded()

  PARANOIA_REALMNAME = GetRealmName();
  
  local englishFaction, localizedFaction = UnitFactionGroup("player")
  if (englishFaction == "Horde") then 
    PARANOIA_OPPFACTION = FACTION_ALLIANCE;
    PARANOIA_OPPFACTIONENG = "Alliance";
  else
    PARANOIA_OPPFACTION = FACTION_HORDE; 
    PARANOIA_OPPFACTIONENG = "Horde";
  end
  -- set default settings if first run
  if (Paranoia_Config.enabled == nil) then Paranoia_Config.enabled = true; end
  if (Paranoia_Config.disableInBattlefield == nil) then Paranoia_Config.disableInBattlefield = true; end
  if (Paranoia_Config.disableInArena == nil) then Paranoia_Config.disableInArena = true; end
  if (Paranoia_Config.disableInSanctuaries == nil) then Paranoia_Config.disableInSanctuaries = true; end
  if (Paranoia_Config.disableInFFA == nil) then Paranoia_Config.disableInFFA = true; end
  if (Paranoia_Config.hideList == nil) then Paranoia_Config.hideList = false; end
  if (Paranoia_Config.listLocked == nil) then Paranoia_Config.listLocked = false; end
  if (Paranoia_Config.enableSound == nil) then Paranoia_Config.enableSound = true; end
  if (Paranoia_Config.inactivityFade == nil) then Paranoia_Config.inactivityFade = true; end
  if (Paranoia_Config.borderOpacity == nil) then Paranoia_Config.borderOpacity = 1; end
  if (Paranoia_Config.frameOpacity == nil) then Paranoia_Config.frameOpacity = 1; end
  if (Paranoia_Config.alertPopupEnabled == nil) then Paranoia_Config.alertPopupEnabled = true; end
  if (Paranoia_Config.alertPopupfadeTime == nil) then Paranoia_Config.alertPopupfadeTime = 2; end
  if (Paranoia_Config.alertPopupdisplayTime == nil) then Paranoia_Config.alertPopupdisplayTime = 3; end
  if (Paranoia_Config.alertPopupAlpha == nil) then Paranoia_Config.alertPopupAlpha = 1; end
  if (Paranoia_Config.listTimeout == nil) then Paranoia_Config.listTimeout = 60; end
  if (Paranoia_Config.warnTimeout == nil) then Paranoia_Config.warnTimeout = 60; end
  if (Paranoia_Config.maxHostiles == nil) then Paranoia_Config.maxHostiles = 15; end
  if (Paranoia_Config.customMsg == nil) then Paranoia_Config.customMsg = PARANOIA_DEFAULTMSG; end
  if (Paranoia_Config.listScale == nil) then Paranoia_Config.listScale = 1.0; end
  if (Paranoia_Config.enableComm == nil) then Paranoia_Config.enableComm = true; end
  if (Paranoia_Config.partyWarn == nil) then Paranoia_Config.partyWarn = true; end
  if (Paranoia_Config.guildWarn == nil) then Paranoia_Config.guildWarn = true; end
  if (Paranoia_Config.minimapPos == nil) then Paranoia_Config.minimapPos = 172; end
  if (Paranoia_Config.hideMinimap == nil) then Paranoia_Config.hideMinimap = false; end
  
  -- apply some settings
  if (Paranoia_Config.enabled) then
    ShowUIPanel(Paranoia_Main);
  else
    HideUIPanel(Paranoia_Main);
  end
  ShowUIPanel(Paranoia_Main);
  if (Paranoia_Config.listLocked) then
    Paranoia:setLock(true);
  else
    Paranoia:setLock(false);
  end
  Paranoia_Main:SetBackdropBorderColor(255, 255, 255, Paranoia_Config.borderOpacity);
  Paranoia_Main:SetAlpha(Paranoia_Config.frameOpacity);
  Paranoia_Main:SetScale(Paranoia_Config.listScale);
  Paranoia_Warn:SetAlpha(Paranoia_Config.alertPopupAlpha);
  
  edb = Paranoia_edb;
  
  Paranoia:createBlizOptions();
  Paranoia:loadDatabase();
  Paranoia:MinimapButton_Reposition();
  Paranoia:UpdateMinimapVisibility();
  
  haveVarsBeenLoaded = true;
  
  Paranoia_updater:SetScript("OnUpdate", function() Paranoia:OnUpdate(); end);
end

function Paranoia:loadDatabase()
  if Paranoia_edb == nil then Paranoia:createDefaultDB(); end
  if Paranoia_edb.dbversion ~= PARANOIA_DBVERSION then
    Paranoia:Msg("Enemy database is not compatible with this version of Paranoia.")
    Paranoia:createDefaultDB();
  end
  if not Paranoia_edb.players[PARANOIA_REALMNAME] then
    Paranoia:createNewRealm(PARANOIA_REALMNAME);
  end
end

function Paranoia:createBlizOptions()

  -- create the interface options panels, they'll appear in inverse order, with the ones added first appearing last.
  local frame = CreateFrame("FRAME", nil);
  frame:SetScript("OnShow",function() Paranoia:ShowConfig() end);
  frame.name = "Paranoia";
  InterfaceOptions_AddCategory(frame);
  
  ParanoiaConf_Frame.name = PARANOIA_TABGENERAL;
  ParanoiaConf_Frame.parent = "Paranoia";
  ParanoiaConf_Frame.default = function() Paranoia:ResetSettings() end
  ParanoiaConf_Frame.okay = function() Paranoia:WarnFrameHide(false) end
  ParanoiaConf_Frame.cancel = function() Paranoia:WarnFrameHide(true) end
  InterfaceOptions_AddCategory(ParanoiaConf_Frame);
  
  ParanoiaConf_Appearance.name = PARANOIA_TABAPPEARANCE;
  ParanoiaConf_Appearance.parent = "Paranoia";
  ParanoiaConf_Appearance.default = function() Paranoia:ResetSettings() end
  ParanoiaConf_Appearance.okay = function() Paranoia:WarnFrameHide(false) end
  ParanoiaConf_Appearance.cancel = function() Paranoia:WarnFrameHide(true) end
  InterfaceOptions_AddCategory(ParanoiaConf_Appearance);
  
  ParanoiaConf_AlertPopup.name = PARANOIA_TABALERTPOPUP;
  ParanoiaConf_AlertPopup.parent = "Paranoia";
  ParanoiaConf_AlertPopup.default = function() Paranoia:ResetSettings() end
  ParanoiaConf_AlertPopup.okay = function() Paranoia:WarnFrameHide(false) end
  ParanoiaConf_AlertPopup.cancel = function() Paranoia:WarnFrameHide(true) end
  InterfaceOptions_AddCategory(ParanoiaConf_AlertPopup);
  
  ParanoiaConf_Announce.name = PARANOIA_TABANNOUNCE;
  ParanoiaConf_Announce.parent = "Paranoia";
  ParanoiaConf_Announce.default = function() Paranoia:ResetSettings() end
  ParanoiaConf_Announce.okay = function() Paranoia:WarnFrameHide(false) end
  ParanoiaConf_Announce.cancel = function() Paranoia:WarnFrameHide(true) end
  InterfaceOptions_AddCategory(ParanoiaConf_Announce);
  
  ParanoiaConf_Communication.name = PARANOIA_TABCOMM;
  ParanoiaConf_Communication.parent = "Paranoia";
  ParanoiaConf_Communication.default = function() Paranoia:ResetSettings() end
  ParanoiaConf_Communication.okay = function() Paranoia:WarnFrameHide(false) end
  ParanoiaConf_Communication.cancel = function() Paranoia:WarnFrameHide(true) end
  InterfaceOptions_AddCategory(ParanoiaConf_Communication);
end

--===================================================================================================================--
--  4) Database Functions
--===================================================================================================================--

function Paranoia:createDefaultDB() --clears the DB and creates a fresh copy with no realms or players.

  Paranoia_edb = {
    dbversion = 1.1,
    players = {},
  }
  edb = Paranoia_edb;
  
end

function Paranoia:createNewRealm(realmname) --adds a new realm to the db
  
  edb.players[realmname] = {
    Horde = {},
    Alliance = {},
    TotalKills = 0,
    TotalDeaths = 0,
  };
  
  Paranoia:Msg("Created a new database for "..realmname..".");
  
end

function Paranoia:createNewPlayer(name, level, class, guild, lastSeen, kos) --creates a new player with the given data
  
  if Paranoia:IsPlayerInBattlefield() then return; end
  if Paranoia:IsPlayerInArena() then return; end
  if Paranoia:IsPlayerInFFA() then return; end
  
  if edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name] ~= nil then error("[Paranoia] createNewPlayer failed. ("..name.." already exists)"); end
  
  edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name] = {};
  edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][1] = level;         --integer
  edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][2] = class;         --integer
  edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][3] = guild;         --string
  edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][4] = lastSeen;      --integer
  edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][5] = kos;           --bool
  edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][6] = 0;             --integer (kills)
  edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][7] = 0;             --integer (deaths)
  edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][8] = "";            --string (player note)
  
  
end

function Paranoia:updatePlayer(name, level, class, guild, lastSeen, kos, kills, deaths, note) --updates a player in the database. any values that are passed nil will not be changed.
  
  if Paranoia:IsPlayerInBattlefield() then return; end
  if Paranoia:IsPlayerInArena() then return; end
  if Paranoia:IsPlayerInFFA() then return; end
    
  if edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name] ~= nil then
    if level then edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][1] = level; end
    if class then edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][2] = class; end
    if guild then edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][3] = guild; end
    if lastSeen then edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][4] = lastSeen; end
    if kos ~= nil then edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][5] = kos; end
    if kills then edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][6] = kills; end
    if deaths then edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][7] = deaths; end
    if note then edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][8] = note; end
  end
  
end

function Paranoia:incrementKill(name) --increments the kill count of the given player by 1.
  
  if Paranoia:IsPlayerInBattlefield() then return; end
  if Paranoia:IsPlayerInArena() then return; end
  if Paranoia:IsPlayerInFFA() then return; end
  
  if edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name] ~= nil then
    edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][6] = edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][6] + 1;
    edb.players[PARANOIA_REALMNAME].TotalKills = edb.players[PARANOIA_REALMNAME].TotalKills + 1;
  end
end

function Paranoia:incrementDeath(name)  --increments the death count of the given player by 1.
  
  if Paranoia:IsPlayerInBattlefield() then return; end
  if Paranoia:IsPlayerInArena() then return; end
  if Paranoia:IsPlayerInFFA() then return; end
  
  if edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name] ~= nil then
    edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][7] = edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][7] + 1;
    edb.players[PARANOIA_REALMNAME].TotalDeaths = edb.players[PARANOIA_REALMNAME].TotalDeaths + 1;
  end
end

function Paranoia:setKoS(name, isKoS)

  if isKoS == true then
    if edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][5] == true then return; end
    edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][5] = true;
    Paranoia:Msg(name.." is now flagged as Kill-on-Sight.");
  else
    if edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][5] == false then return; end
    edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][5] = false;
    Paranoia:Msg(name.." is no longer flagged as Kill-on-Sight.");
  end

end

function Paranoia:deletePlayer(name)  --removes a player from the database.
  
  assert(edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name], "[Paranoia] deletePlayer failed. ("..name.." does not exist)");
  
  edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name] = nil;
  
end

function Paranoia:getPlayerLevelClass(name) --returns level and class.

  if edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name] ~= nil then
    return edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][1], edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name][2];
  end
  return nil;
  
end


--===================================================================================================================--
--  5) Miscellaneous Helper Functions
--===================================================================================================================--

function Paranoia:ButtonClick(self, name)
    if (string.find(name, "emptyButton") ~= nil) then return; end
    Paranoia:dMsg("Class= "..activeHostileList[name].class.." classKnown="..tostring(activeHostileList[name].classKnown));
    Paranoia:dMsg("Level= "..activeHostileList[name].level.." levelDisp="..activeHostileList[name].level.." levelKnown="..tostring(activeHostileList[name].levelKnown));
    Paranoia:dMsg("Zone= "..activeHostileList[name].zone.." X Coordinate="..activeHostileList[name].x.." Y Coordinate="..tostring(activeHostileList[name].y));
    if self:CanChangeProtectedState() then
      self:SetAttribute("macrotext", SLASH_TARGET1.." "..tostring(name));
      Paranoia:dMsg("Attempting to target "..tostring(name));
    else
      Paranoia:Msg(PARANOIA_CHATTARGETDISABLED);
    end
end

function Paranoia:commMsg(command, msg, channel, priority, whisperTar)
  if priority == nil then priority = "BULK"; end
  ChatThrottleLib:SendAddonMessage(priority, "PEPA", PARANOIA_COMMVERSION.."%"..GetLocale().."%"..command.."%"..msg, string.upper(channel), whisperTar);
end

function Paranoia:dMsg(msg)
  if debugEnabled then DEFAULT_CHAT_FRAME:AddMessage("|cFF9482CAPEPADebug: "..msg.."|r"); end
end

function Paranoia:DoResetFrames()
  Paranoia_Warn:SetUserPlaced(false);
  Paranoia_Main:SetUserPlaced(false);
  ReloadUI();
end

function Paranoia:DoResetSettings()
  Paranoia_Config = nil;
  Paranoia_Warn:SetUserPlaced(false);
  Paranoia_Main:SetUserPlaced(false);
  ReloadUI()
end

function Paranoia:strLevel(name, level, source) --This converts a numeric level returned from UnitLevel to a string level.
  Paranoia:dMsg("name: "..name.." | level: "..level.." | source: "..source);
  if activeHostileList[name].levelKnown ~= true then
    if (level == 80) then                         --level is 80, we don't append a + sign and we set levelKnown to true.
      activeHostileList[name].level = 80;
      activeHostileList[name].levelDisp = 80;
      activeHostileList[name].levelKnown = true;
    elseif (level == -1) then                     --level is -1, so player is at least 10 levels higher. levelDisp = player level+10.
      activeHostileList[name].level = level;
      activeHostileList[name].levelDisp = (UnitLevel("player") + 10).."+";
    elseif (level ~= 0) then                      --level is known or guessed.
      activeHostileList[name].level = level;
      if source ~= 1 then                         --source is not a mouseover/target, so it must have been a guess. levelknown = false, append a + sign.
        activeHostileList[name].levelDisp = level.."+";
        activeHostileList[name].levelKnown = false;
      else                                        --source is a mouseover/target and level is 100% sure. levelKnown = true.
        activeHostileList[name].levelDisp = level;
        activeHostileList[name].levelKnown = true;
      end
    else                                          --level is completely unknown (detected via environmental dmg/melee swing)
      activeHostileList[name].level = 0;
      activeHostileList[name].levelKnown = false;
      activeHostileList[name].levelDisp = '?';
    end
  end
end

function Paranoia:numLevel(value) --Strips the + sign from the level.
  if strlen(value) == 2 then
    if strsub(value, 2, 2) == "+" then return tonumber(strsub(value, 1, 1)); end
  else
    if strsub(value, 3, 3) == "+" then return tonumber(strsub(value, 1, 2)); end
  end
end

function Paranoia:IsGuildieNearby(name)
  GuildRoster();
  for i = 1, (GetNumGuildMembers(true)) do
    local gname, _, _, _, _, gzone, _, _, _, _ = GetGuildRosterInfo(i)
    if gname == name then 
      if gzone ~= GetZoneText() then return(false); end
      return(true);
    end
  end
  return(false);
end

function Paranoia:IsPlayerInArena()
  if (IsActiveBattlefieldArena()) then
    return(true);
  end
  return(false);
end

function Paranoia:IsPlayerInBattlefield()
  local inInstance, instanceType = IsInInstance()
  if inInstance ~= nil and instanceType == "pvp" then return(true); else return(false); end
  return(false);
end

function Paranoia:IsPlayerInFFA()
    local isFFA = UnitIsPVPFreeForAll("player")
    if isFFA == nil then isFFA = false; end
    return isFFA;
end

function Paranoia:IsPlayerInInstance()
  local inInstance, instanceType = IsInInstance()
  if inInstance ~= nil and (instanceType == "party" or instanceType == "raid") then return(true); else return(false); end
  return(false);  
end


function Paranoia:IsPlayerInSanctuary()
    local pvpType, _, _ = GetZonePVPInfo();
    if (pvpType == "sanctuary") then
        return(true);
    else
        return(false);
    end
end

function Paranoia:Msg(msg)
  msg = tostring(msg);
  DEFAULT_CHAT_FRAME:AddMessage("|cFF9482CAParanoia:|r |cFFFF7D0A"..msg.."|r");
end

function Paranoia:ResetFrames()
  StaticPopup_Show("RELOADUIPROMPT");
end

function Paranoia:ResetSettings()
  StaticPopup_Show ("RELOADUIPROMPT2");
  Paranoia_Warn:Clear();
end

function Paranoia:setLock(lock)
  local i = 1;
  if (lock) then
    Paranoia_Main:RegisterForDrag();
    while (i <= 15) do        --15 is the maximum number of lines allowed by the paranoia code
      button = getglobal("Paranoia_StatusFrame"..i);
      button:RegisterForDrag();
      i = i + 1;
    end
  else
    Paranoia_Main:RegisterForDrag("LeftButton");
    while (i <= 15) do        --15 is the maximum number of lines allowed by the paranoia code
      button = getglobal("Paranoia_StatusFrame"..i);
      button:RegisterForDrag("LeftButton");
      i = i + 1;
    end
  end
end

function Paranoia:ClassColor(class)
  if (strsub(class,1,4) == PARANOIA_WARLOCK_SHORT) then colorR = RAID_CLASS_COLORS["WARLOCK"].r; colorG = RAID_CLASS_COLORS["WARLOCK"].g; colorB = RAID_CLASS_COLORS["WARLOCK"].b;
  elseif (strsub(class,1,4) == PARANOIA_DRUID_SHORT) then colorR = RAID_CLASS_COLORS["DRUID"].r; colorG = RAID_CLASS_COLORS["DRUID"].g; colorB = RAID_CLASS_COLORS["DRUID"].b;
  elseif (strsub(class,1,4) == PARANOIA_HUNTER_SHORT) then colorR = RAID_CLASS_COLORS["HUNTER"].r; colorG = RAID_CLASS_COLORS["HUNTER"].g; colorB = RAID_CLASS_COLORS["HUNTER"].b;
  elseif (strsub(class,1,4) == PARANOIA_MAGE_SHORT) then colorR = RAID_CLASS_COLORS["MAGE"].r; colorG = RAID_CLASS_COLORS["MAGE"].g; colorB = RAID_CLASS_COLORS["MAGE"].b;
  elseif (strsub(class,1,4) == PARANOIA_PALADIN_SHORT) then colorR = RAID_CLASS_COLORS["PALADIN"].r; colorG = RAID_CLASS_COLORS["PALADIN"].g; colorB = RAID_CLASS_COLORS["PALADIN"].b;
  elseif (strsub(class,1,4) == PARANOIA_PRIEST_SHORT) then colorR = RAID_CLASS_COLORS["PRIEST"].r; colorG = RAID_CLASS_COLORS["PRIEST"].g; colorB = RAID_CLASS_COLORS["PRIEST"].b;
  elseif (strsub(class,1,4) == PARANOIA_ROGUE_SHORT) then colorR = RAID_CLASS_COLORS["ROGUE"].r; colorG = RAID_CLASS_COLORS["ROGUE"].g; colorB = RAID_CLASS_COLORS["ROGUE"].b;
  elseif (strsub(class,1,4) == PARANOIA_SHAMAN_SHORT) then colorR = RAID_CLASS_COLORS["SHAMAN"].r; colorG = RAID_CLASS_COLORS["SHAMAN"].g; colorB = RAID_CLASS_COLORS["SHAMAN"].b;
  elseif (strsub(class,1,4) == PARANOIA_WARRIOR_SHORT) then colorR = RAID_CLASS_COLORS["WARRIOR"].r; colorG = RAID_CLASS_COLORS["WARRIOR"].g; colorB = RAID_CLASS_COLORS["WARRIOR"].b;
  elseif (strsub(class,1,4) == PARANOIA_DEATHKNIGHT_SHORT) then colorR = RAID_CLASS_COLORS["DEATHKNIGHT"].r; colorG = RAID_CLASS_COLORS["DEATHKNIGHT"].g; colorB = RAID_CLASS_COLORS["DEATHKNIGHT"].b;
  else colorR = 0.6; colorG = 0.6; colorB = 0.6; end
  return colorR, colorG, colorB;
end

function Paranoia:showList(enabled)
  if (Paranoia_Config.hideList == false) then
    ShowUIPanel(Paranoia_Main);
    UIFrameFadeIn(Paranoia_Main, 0.2, 0, Paranoia_Config.frameOpacity)
  end
  if (enabled) then isParanoiaDisabled = false; end
end

function Paranoia:hideList(disabled)
  if Paranoia:round(Paranoia_Main:GetAlpha(), 2) == Paranoia:round(Paranoia_Config.frameOpacity, 2) then
    fadeStart = GetTime();
    UIFrameFadeOut(Paranoia_Main, 0.2, Paranoia_Config.frameOpacity, 0);
  end
  if (disabled) then isParanoiaDisabled = true; end
end

function Paranoia:round(num, decimalplaces)
  local mult = 10^(decimalplaces or 0)
  return math.floor(num * mult + 0.5) / mult
end

function Paranoia:TimeToString(time, useShort)
  local secondsString, minutesString, hoursString, daysString;
  if not useShort then
    secondsString = "seconds";
    minutesString = "minutes";
    hoursString = "hours";
    daysString = "days"
  else
    secondsString = "s";
    minutesString = "m";
    hoursString = "h";
    daysString = "d";
  end
  local ret = "";
  local units = 0;

  if( time < 0 ) then
    return "";
  end

  local days = floor( time / 86400 );

  if( days > 0 ) then
    time = time - days * 86400;
    ret = days..daysString.." ";
    units = units + 1;
  end

  local hours = floor( time / 3600 );
  if( hours > 0 ) then
    time = time - hours * 3600;
    ret = ret .. hours..hoursString.." ";
    units = units + 1;
  end

  if( units == 2 ) then
    return ret;
  end

  local minutes = floor ( time / 60 );
  if( minutes > 0 ) then
    time = time - minutes * 60;
    ret = ret .. minutes..minutesString.." ";
    units = units + 1;
  end

  if( units == 2 ) then
    return ret;
  end

  return ret .. time..secondsString;
end

--Returns a true boolean value for GetChecked instead of 1nil.
function Paranoia:ToBoolean(value)
  if (value == nil) then
    return(false);
  end
  if (type(value) == 'boolean') then
    return(value);
  end
  return(true);
end

function Paranoia:updateVisibility()
  --If any of these conditions are true we hide Paranoia and exit the function.
  if Paranoia_Config.enabled == false then 
    Paranoia:hideList(true); 
    return; 
  end
  if Paranoia_Config.disableInBattlefield and Paranoia:IsPlayerInBattlefield() then 
    Paranoia:hideList(true); 
    return; 
  end
  if Paranoia_Config.disableInArena and Paranoia:IsPlayerInArena() then 
    Paranoia:hideList(true); 
    return; 
  end
  if Paranoia_Config.disableInFFA and Paranoia:IsPlayerInFFA() then 
    Paranoia:hideList(true); 
    return; 
  end
  if Paranoia_Config.disableInSanctuaries and Paranoia:IsPlayerInSanctuary() == true then 
    Paranoia:hideList(true); 
    return; 
  end
  if Paranoia_Config.hideList then 
    Paranoia:hideList(false); 
    return; 
  end --Passing false to hideList will hide Paranoia but not disable it.
  
  --We've passed the visibility test so we make the frame visible.
  if Paranoia_Main:GetAlpha() == 0 then Paranoia:showList(true); end;

end

--===================================================================================================================--
--  6) Slash Handling
--===================================================================================================================--

--The code below for parsing slash command arguments was written by Tigerheart.
function Paranoia:GetArgument(msg)
 	if msg then
 		local a,b=strfind(msg, "(%S+)");
 		if a then
 			return strsub(msg,1,a-1), strsub(msg, b+1);
 		else	
 			return "";
 		end
 	end
end

function Paranoia:GetCmd(msg)
  if msg then
 		local a,b,c=strfind(msg, "(%S+)"); 
 		if a then
 			return c, strsub(msg, b+2);
 		else	
 			return "";
 		end
 	end
end

function Paranoia:SlashHandler(arg)
  local cmd, subCmd = Paranoia:GetCmd(arg);
  if arg then
    if arg ~= "testwarn" then arg = string.lower(arg); end
  end
  if (cmd == PARANOIA_SLACONFIG) then --config
    Paranoia:ShowConfig();
  elseif (cmd == PARANOIA_SLAENABLE) then--enable
    Paranoia_Config.enabled = true;
    Paranoia:OnUpdate();
    Paranoia:Msg(PARANOIA_CHATENABLED);
  elseif (cmd == PARANOIA_SLADISABLE) then --disable
    Paranoia_Config.enabled = false;
    Paranoia:Msg(PARANOIA_CHATDISABLED);
  elseif (cmd == PARANOIA_SLADEBUG) then --debug
    if (debugEnabled) then 
      debugEnabled = false;
      verboseEnabled = false;
      Paranoia:Msg(PARANOIA_CHATDEBUGDISABLED);
    else
      debugEnabled = true;
      Paranoia:Msg(PARANOIA_CHATDEBUGENABLED);
    end
  elseif (cmd == "verbose") then
    if (verboseEnabled) then 
      verboseEnabled = false;
      Paranoia:Msg("Verbose debug mode disabled.");
    else
      verboseEnabled = true;
      debugEnabled = true;
      Paranoia:Msg("Verbose debug mode enabled.");
    end
  elseif (cmd == "debuglist") then    -- not included in localization
    Paranoia:Msg("Debug Command List");
    DEFAULT_CHAT_FRAME:AddMessage("------------------------------------");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia testwarn [name] [class] [level]|r\nSimulates a test hostile player. With no arguments, uses Testplayer1-10. ");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia rui|r\nReloads the user interface. ");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia default|r\nResets settings to default values (reloads UI).");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia clear|r\nResets timeouts, clears the enemy/ignore lists, and unloads the SpellList.");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia cleardb|r\nClears the entire enemy database (reloads UI).");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia clearrealm|r\nClears the enemy database for this realm only.");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia verbose|r\nToggles verbose debug mode (displays data about all incoming combatlog events).");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia gc|r\nForces a garbage collection.");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia mem|r\nDisplays Paranoia's memory usage.");
    if GetLocale() ~= "enUS" and GetLocale() ~= "enGB" then DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia ping [party|guild|player] [playername]|r\nLooks for other players using Paranoia."); end
  elseif (cmd == "testwarn") then
    local testName, testInfo = Paranoia:GetCmd(subCmd);
    if testName == "" then
      Paranoia:Msg("Calling Paranoia:warnHostilePlayer (using Testplayer1-10)");
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer", PARANOIA_DRUID, 4, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer2", PARANOIA_MAGE,68, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer3", PARANOIA_PALADIN, 35, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer4", PARANOIA_ROGUE, 10, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer5", PARANOIA_SHAMAN, 60, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer6", PARANOIA_WARLOCK, 41, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer7", PARANOIA_WARRIOR, 52, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer8", PARANOIA_PRIEST, 21, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer9", PARANOIA_HUNTER, 70, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer10", nil, 50, 0);
    elseif testName ~= "" then
      local testClass, testLevel = Paranoia:GetCmd(testInfo);
      Paranoia:Msg("Calling Paranoia:warnHostilePlayer (using "..testName..")");
      Paranoia:warnHostilePlayer("User initiated test warning", testName, tostring(testClass), testLevel, 1);
    end
  elseif (cmd == "rui") then
    ReloadUI()
  elseif (cmd == "default") then
    Paranoia:ResetSettings()
  elseif (cmd == "clear") then
    Paranoia_SpellList = nil;
    activeHostileList = {};
    ignoreList = {};
    lastWarning = 0;
    Paranoia:Msg("Enemy player table cleared.");
  elseif (cmd == "cleardb") then
    Paranoia:createDefaultDB();
    ReloadUI();
  elseif (cmd == "clearrealm") then
    Paranoia:createNewRealm(PARANOIA_REALMNAME);
    Paranoia:Msg("All player data for this realm has been cleared.");
  elseif (cmd == "gc") then
    UpdateAddOnMemoryUsage();
    Paranoia:Msg("Forcing garbage collection... (mem usage: "..Paranoia:round(GetAddOnMemoryUsage("Paranoia_EPA"),2).." KB)");
    collectgarbage();
    UpdateAddOnMemoryUsage();
    Paranoia:Msg("Garbage collection finished. (mem usage: "..Paranoia:round(GetAddOnMemoryUsage("Paranoia_EPA"),2).." KB)");
  elseif (cmd == "mem") then
    UpdateAddOnMemoryUsage();
    Paranoia:Msg("Memory Usage: "..Paranoia:round(GetAddOnMemoryUsage("Paranoia_EPA"),2).." KB\n Type /para gc for garbage collection.");
  elseif cmd == "ping" then
    if isPingWaiting then
      Paranoia:Msg("Paranoia is waiting for a reply for a previous ping... please wait.");
      return;
    end
    local pingCmd, pingTarget = Paranoia:GetCmd(subCmd);
    if pingCmd == "party" then
      if GetNumPartyMembers() == 0 then
        Paranoia:Msg("You are not in a party.");
        return;
      end
      Paranoia:Msg("Pinging party members, please wait a moment...");
      Paranoia:commMsg("PING", UnitName("player"), "PARTY", "ALERT");
      gotFirstReply = false;
      lastReplyTime = GetTime();
      isPingWaiting = true;
    elseif pingCmd == "guild" then
      if IsInGuild() == nil then
        Paranoia:Msg("You are not in a guild.")
        return;
      end
      Paranoia:Msg("Pinging guild members, please wait a moment...");
      Paranoia:commMsg("PING", UnitName("player"), "GUILD", "ALERT");
      gotFirstReply = false;
      lastReplyTime = GetTime();
      isPingWaiting = true;
    elseif pingCmd == "player" then
      if pingTarget == "" then
        Paranoia:Msg("You must specify a player.");
        return;
      end
      if strlower(pingTarget) == strlower(UnitName("player")) then
        Paranoia:Msg("You are probably using Paranoia.");
        return;
      end
      Paranoia:Msg("Pinging player, please wait a moment...");
      Paranoia:commMsg("PING", UnitName("player"), "WHISPER", "ALERT", pingTarget);
      gotFirstReply = false;
      lastReplyTime = GetTime();
      isPingWaiting = true;
    else
      Paranoia:Msg("You must specify party, guild, or player.");
    end
  elseif cmd == nil or cmd == "" then
    Paranoia:Msg(PARANOIA_CHATSLASHLIST);
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A"..PARANOIA_CHATSLASHLISTCONFIG);
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A"..PARANOIA_CHATSLASHLISTENABLE);
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A"..PARANOIA_CHATSLASHLISTDISABLE);
    if GetLocale() == "enUS" or GetLocale() == "enGB" then DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia ping [party|guild|player] [playername]|r\nLooks for other players using Paranoia."); end
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A"..PARANOIA_CHATSLASHLISTDEBUG);
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A"..PARANOIA_CHATSLASHLISTDEBUGL);
  else
    Paranoia:Msg(string.format(PARANOIA_CHATUNRECOGNIZED, cmd));
  end
  
end



--===================================================================================================================--
--  7) Hostile Handling Functions
--===================================================================================================================--
--*********************************************************************************************
--*********************************** REWRITE ME!!!! ******************************************
--*********************************************************************************************
--need to fix:
--adding/updating/retrieving players from database
--converting levels returned from unitlevel
--updating data in the right situations
--split up warnhostileplayer into different functions, clean it up

function Paranoia:addNotHostile(name, expiry)
  if expiry == nil then expiry = (60*60*3); end
  if (ignoreList[name]) then
    ignoreList[name].time = GetTime();
  else
    ignoreList[name] = { time = GetTime(), timeout = tonumber(expiry) };
  end
end

function Paranoia:partyMembersChanged()
  local i;
  Paranoia:addNotHostile(UnitName('player'), (60*60*3));
  if (UnitExists('pet')) then
    Paranoia:addNotHostile(UnitName('pet'), (60*60*3));
  end
  for i = 1, GetNumPartyMembers() do
    if (UnitExists('party'..i)) then
      Paranoia:addNotHostile(UnitName('party'..i), (60*60*3));
    end
    if (UnitExists('partypet'..i)) then
      Paranoia:addNotHostile(UnitName('partypet'..i), (60*60*3));
    end
  end
end

--this whole stupid thing needs to be rewritten, its bloated and ugly as hell, just like onupdate was. 
--i'll do it one day when I have lots of time to kill. Sorry for the excessive commenting, but it's the
--only way I'll be able to follow the code.
function Paranoia:warnHostilePlayer(event, name, class, level, source, sender, guild) --source is 0 if combat log event, 1 if mouseover/target, 2 if from party, 3 if from guild.
  if not (Paranoia_Config.enabled and name) then
    return;
  end
  
  if (class == nil or class == "") then class = UNKNOWN; end                               
  if (level == nil or level == "") then level = 0; end  
  if (guild == nil) then guild = ""; end
  
  Paranoia:dMsg("warnHostilePlayer: event: "..event.." \nname: "..name.." \nclass: "..class.." \nlevel: "..level);
  
  if (activeHostileList[name]) then  --the player is already on the enemy list, update info.
  
    activeHostileList[name].time = GetTime();  --update time
    
    local posX, posY = GetPlayerMapPosition("player");  --update positioning
    posX=floor(posX *100);
    posY=floor(posY *100);
    activeHostileList[name].x = posX;
    activeHostileList[name].y = posY;
    activeHostileList[name].zone = GetMinimapZoneText();
    
    if (class ~= UNKNOWN) then  --update class
      activeHostileList[name].class = class;
    end
    
    if type(level) == "string" then level = tonumber(level); end  --convert level to a number
    if type(activeHostileList[name].level) == "string" then activeHostileList[name].level = tonumber(activeHostileList[name].level); end
    
    if (level ~= 0 and activeHostileList[name].levelKnown == false and level > activeHostileList[name].level) then
      activeHostileList[name].level = level;
      Paranoia:strLevel(name, level, source);
    end
    
    -- communication
    if (GetTime() - activeHostileList[name].lastComm > 6 or source == 1) and (Paranoia_Config.enableComm) and (source < 2) then
      Paranoia:commMsg("DATA", name.."^"..activeHostileList[name].class.."^"..activeHostileList[name].level.."^"..activeHostileList[name].x..",".."^"..activeHostileList[name].y, "PARTY");
      if IsInGuild() ~= nil then Paranoia:commMsg("DATA", name.."^"..activeHostileList[name].class.."^"..activeHostileList[name].level.."^"..activeHostileList[name].x..",".."^"..activeHostileList[name].y, "GUILD"); end
      activeHostileList[name].lastComm = GetTime();
    end
    
    Paranoia:updatePlayer(name, Paranoia:numLevel(activeHostileList[name].levelDisp), activeHostileList[name].class, guild, time())
  elseif (ignoreList[name] and not Paranoia:IsPlayerInFFA()) then --player is on the ignore list (we ignore the ignore list when in FFA zones)
    ignoreList[name].time = GetTime();
    Paranoia:OnUpdate();
    return;
  else  --player is not on the hostile list, and is not on the ignore list, so we'll add them.
    Paranoia:addNewHostile(name, class, level, source, guild);
  end

  if (GetTime() - lastWarning > Paranoia_Config.warnTimeout) then
    if not (isParanoiaDisabled) then
      if Paranoia_Config.enableSound then
        Paranoia:dMsg("Playing warning sound...");
        PlaySoundFile("Sound\\Spells\\PVPWarningAlliance.wav");
        lastWarning = GetTime();
      end
      if activeHostileList[name].class == UNKNOWN then
        if (isConfigOpen == false and Paranoia_Config.alertPopupEnabled == true) then
          if source == 2 then 
            Paranoia_Warn:AddMessage(string.format(PARANOIA_PARTYALERTNOCLASS, sender, name, activeHostileList[name].x, activeHostileList[name].y), 255, 0, 0, 1, Paranoia_Config.alertPopupdisplayTime); 
          elseif source == 3 then
            Paranoia_Warn:AddMessage(string.format(PARANOIA_GUILDALERTNOCLASS, sender, name, activeHostileList[name].x, activeHostileList[name].y), 255, 0, 0, 1, Paranoia_Config.alertPopupdisplayTime); 
          else
            Paranoia_Warn:AddMessage(string.format(PARANOIA_ALERTNOCLASS, PARANOIA_OPPFACTION, name), 255, 0, 0, 1, Paranoia_Config.alertPopupdisplayTime); 
          end
        end
        lastWarning = GetTime();
      else
        if (isConfigOpen == false and Paranoia_Config.alertPopupEnabled == true) then 
          if source == 2 then 
            Paranoia_Warn:AddMessage(string.format(PARANOIA_PARTYALERTCLASS, sender, activeHostileList[name].class, name, activeHostileList[name].x, activeHostileList[name].y), 255, 0, 0, 1, Paranoia_Config.alertPopupdisplayTime); 
          elseif source == 3 then
            Paranoia_Warn:AddMessage(string.format(PARANOIA_GUILDALERTCLASS, sender, activeHostileList[name].class, name, activeHostileList[name].x, activeHostileList[name].y), 255, 0, 0, 1, Paranoia_Config.alertPopupdisplayTime); 
          else
            Paranoia_Warn:AddMessage(string.format(PARANOIA_ALERTCLASSKNOWN, PARANOIA_OPPFACTION, activeHostileList[name].class, name), 255, 0, 0, 1, Paranoia_Config.alertPopupdisplayTime); 
          end
        end
        lastWarning = GetTime();
      end
    end
  end
  Paranoia:OnUpdate();
end

function Paranoia:addNewHostile(name, class, level, source, guild)
  if source == nil then source = 0; end
  
  activeHostileList[name] = { time = GetTime() };  --create new player with last activity time
  activeHostileList[name].lastComm = GetTime();    --set lastcomm time to now
  
  local posX, posY = GetPlayerMapPosition("player");  --updating positioning
  posX=floor(posX *100);
  posY=floor(posY*100);
  activeHostileList[name].x = posX;
  activeHostileList[name].y = posY;
  activeHostileList[name].zone = GetMinimapZoneText();
  
  if Paranoia_edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name] ~= nil then --player exists in the database.
    if source ~= 1 and level ~= -1 then --source 1 is mouseover/target, and the player is not skulled, so that means we already know the level and don't need to retrieve it.
      local tempLevel, tempClass;
      tempLevel, tempClass = Paranoia:getPlayerLevelClass(name);
      tempLevel = tonumber(tempLevel)
      if tempLevel == nil then tempLevel = 0; end
      Paranoia:dMsg("Retreived "..name.."'s level from database ("..tempLevel..")... var level="..level.." | type="..type(level));
      if (tempLevel ~= 0) and (tempLevel > level) then level = tempLevel; end
      if (tempClass ~= UNKNOWN) then class = tempClass; end
      tempClass = nil;
      tempLevel = nil;
    end
  end
  
  if (class ~= UNKNOWN) then
    activeHostileList[name].class = class;
  else
    activeHostileList[name].class = UNKNOWN;
  end

  Paranoia:strLevel(name, level, source);
  
  if Paranoia_edb.players[PARANOIA_REALMNAME][PARANOIA_OPPFACTIONENG][name] == nil then
    Paranoia:createNewPlayer(name, Paranoia:numLevel(activeHostileList[name].levelDisp), activeHostileList[name].class, guild, time(), false);
  end
  
  if Paranoia_Config.enableComm and source < 2 then
    Paranoia:commMsg("DATA", name.."^"..activeHostileList[name].class.."^"..activeHostileList[name].level.."^"..activeHostileList[name].x..",".."^"..activeHostileList[name].y, "PARTY");
    if IsInGuild() ~= nil then Paranoia:commMsg("DATA", name.."^"..activeHostileList[name].class.."^"..activeHostileList[name].level.."^"..activeHostileList[name].x..",".."^"..activeHostileList[name].y, "GUILD"); end
    activeHostileList[name].lastComm = GetTime();
  end
end

function Paranoia:updateHostileList()
  if isParanoiaDisabled then return; end
  -- This function is called every OnUpdate().
  local buttonText, i, displayed;
  local dispName, class;
  local paraWidth = 0;
  displayed = 0;
  i = 1;
  
  table.sort(activeHostileList);
  for name,_ in pairs(activeHostileList) do
    if (activeHostileList[name].time < GetTime() - Paranoia_Config.listTimeout) then
      -- Enemy has reached the time limit for inactivity... remove them from the enemy table.
      activeHostileList[name] = nil;
      
    elseif (i <= Paranoia_Config.maxHostiles) then
      -- Enemy is still valid. 
      if not InCombatLockdown() then
        buttonFrame = getglobal("Paranoia_StatusFrame"..i);
        buttonText = getglobal("Paranoia_StatusFrame"..i.."Text");
        button = getglobal("Paranoia_StatusFrame"..i.."Button")
        if buttonText:GetStringWidth() + 17 > paraWidth then 
          paraWidth = buttonText:GetStringWidth() + 17;
        end
        buttonFrame:SetWidth(paraWidth);
        buttonText:SetWidth(paraWidth);
      end

      dispName = name;

      -- Set class variable.
      class = activeHostileList[name].class;
      if (not (class)) then
        class = UNKNOWN;
      end

      if not InCombatLockdown() then 
      buttonFrame:Show(); 
      end
      
      
      if (not InCombatLockdown()) then
        button.nickname = name;
        button.id = name;
      end
      
      -- Apply inactivity alpha.
      textAlpha = 1;
      colorR = 1;
      colorG = 1;
      colorB = 0;
      if (Paranoia_Config.inactivityFade) then
        timeSinceLast = Paranoia:round(GetTime() - activeHostileList[name].time,3);
        fadeTicks = timeSinceLast / Paranoia_Config.listTimeout;
        textAlpha = (1 - fadeTicks);
      end
      
      colorR, colorG, colorB = Paranoia:ClassColor(class);
            
      if (not InCombatLockdown()) then
        buttonText:SetText(dispName.." ("..class.."/"..activeHostileList[name].levelDisp..")");
        buttonText:SetTextColor(colorR, colorG, colorB, textAlpha);
      end
      
      displayed = displayed + 1;
      i = i + 1;
    end
    
  end

  if not InCombatLockdown() then 
  Paranoia_Main:SetWidth(paraWidth); 
  end
  
  -- Clear empty buttons.
  while (i <= 15) do        --15 is the maximum number of lines allowed by the paranoia code
    if (not InCombatLockdown()) then
      buttonFrame = getglobal("Paranoia_StatusFrame"..i);
      buttonText = getglobal("Paranoia_StatusFrame"..i.."Text");
      button = getglobal("Paranoia_StatusFrame"..i.."Button");
      if i > 2 then buttonFrame:Hide(); end
      if not InCombatLockdown() then 
        button.nickname = "emptyButton"..i;
        button.id = "emptyid"..i; 
      end
      buttonText:SetText("");
      buttonText:SetTextColor(0.5, 0.5, 0.5);
    end
    i = i + 1;
  end
  
  -- Display No players detected message if all buttons are empty
  if (displayed == 0) then
    displayed = 2;
    if (not InCombatLockdown()) then
      if string.find(PARANOIA_NODETECTL1, "%s", 1, true) ~= nil then  --(since %s is a regex character we must specify plain matching)
        Paranoia_StatusFrame1Text:SetText(string.format(PARANOIA_NODETECTL1, PARANOIA_OPPFACTION));
        Paranoia_StatusFrame2Text:SetText(PARANOIA_NODETECTL2);
      else
        Paranoia_StatusFrame1Text:SetText(PARANOIA_NODETECTL1);
        Paranoia_StatusFrame2Text:SetText(string.format(PARANOIA_NODETECTL2, PARANOIA_OPPFACTION));
      end
      Paranoia_StatusFrame1Text:SetTextColor(0.5, 0.5, 0.5);
      Paranoia_StatusFrame2Text:SetTextColor(0.5, 0.5, 0.5);
      local strWidth = Paranoia_StatusFrame1Text:GetStringWidth();
      if strWidth < Paranoia_StatusFrame2Text:GetStringWidth() then strWidth = Paranoia_StatusFrame2Text:GetStringWidth(); end
      Paranoia_Main:SetWidth(strWidth + 17);
    end
  end
  
  -- Remove ignored/nothostile players from ignore list if three hours has passed.
  for name,_ in pairs(ignoreList) do
    if (ignoreList[name].time < GetTime() - ignoreList[name].timeout) then		-- 3 hours
      ignoreList[name] = nil;
    end
  end

  --Change frame height and width according to buttons
  
  if (displayed < 2) then
    displayed = 2;
  end

  if (not InCombatLockdown()) then 
    Paranoia_Main:SetHeight(10 + displayed*18); 
  end
end

--===================================================================================================================--
--  8) WarnFrame Functions
--===================================================================================================================--

function Paranoia:WarnFrameShow()
  -- "Interface/DialogFrame/UI-DialogBox-Background"
  -- "Interface/Tooltips/UI-Tooltip-Background"
  isConfigOpen = true;
  Paranoia_Warn:Clear();
  Paranoia_Warn:SetBackdrop({bgFile = "Interface/DialogFrame/UI-DialogBox-Background"});
  Paranoia_Warn:EnableMouse(true);
  Paranoia_Warn:Show();
  Paranoia_Warn:SetTimeVisible(500);
  Paranoia_Warn:AddMessage(PARANOIA_ALERTMOVEABLE, 0, 1, 0)
  Paranoia_Main:RegisterForDrag("LeftButton")
end

function Paranoia:WarnFrameTest()
  Paranoia_Warn:Clear();
  Paranoia_Warn:SetTimeVisible(Paranoia_Config.alertPopupdisplayTime);
  Paranoia_Warn:SetFadeDuration(Paranoia_Config.alertPopupfadeTime);
  Paranoia_Warn:AddMessage(string.format(PARANOIA_ALERTTEST, PARANOIA_OPPFACTION), 255, 0, 0 ,100)
end

function Paranoia:WarnFrameHide(cancel)
  isConfigOpen = false;
  Paranoia_Warn:Clear();
  Paranoia_Warn:SetBackdrop(nil);
  Paranoia_Warn:EnableMouse(false);
  Paranoia_Warn:SetTimeVisible(Paranoia_Config.alertPopupdisplayTime);
  Paranoia_Warn:SetFadeDuration(Paranoia_Config.alertPopupfadeTime);
  Paranoia_Main:RegisterForDrag("")
  if cancel then  --replace our modified Paranoia_Config table with our backup copy.
    if configCopy == nil then return; end
    Paranoia_Config = CopyTable(configCopy);
    Paranoia:setLock(configCopy["listLocked"]);
    Paranoia_Main:SetScale(configCopy["listScale"]);
    if ( not (Paranoia_Main:GetAlpha() == 0 ) ) then Paranoia_Main:SetAlpha(configCopy["frameOpacity"]); end
    Paranoia_Main:SetBackdropBorderColor(255, 255, 255, configCopy["borderOpacity"]);
    Paranoia_Warn:SetAlpha(configCopy["alertPopupAlpha"]);
    Paranoia:UpdateMinimapVisibility();
    end
  configCopy = nil;
end

--===================================================================================================================--
--  9) Configuration Frame Functions
--===================================================================================================================--

function Paranoia:OptionCheckBoxClicked(self, option)
  Paranoia_Config[option] = Paranoia:ToBoolean(self:GetChecked());
end

function Paranoia:LockedCheckBoxClicked(self)
  if Paranoia:ToBoolean(self:GetChecked()) then
    Paranoia:setLock(true);
    Paranoia_Config['listLocked'] = true;
  else
    Paranoia:setLock(false);
    Paranoia_Config['listLocked'] = false;
  end
end

function Paranoia:OptionCheckBoxInit(self, option)
  self:SetChecked(Paranoia_Config[option]);
end

function Paranoia:SliderMoved(self, option)
  Paranoia_Config[option] = self:GetValue();
end

function Paranoia:SliderInit(self, option)
  self:SetValue(Paranoia_Config[option]);
end

function Paranoia:ShowConfig()
  --[[
  Upcoming 3.0 Changes - Concise List - 07/18/2008 02:01:11 PM PDT
  
  Interface Options
    * InterfaceOptionsFrame_OpenToPage has been renamed InterfaceOptionsFrame_OpenToCategory !!!
  
  Lol, break a bunch of addons' config frames and have users mass post comments in confusion, 
  but that's okay since the function has been renamed to be a little more indicative of what it does!
  It was awfully confusing as OpenToPage. Good job, Blizzard. :P
  ]]--
  
  configCopy = CopyTable(Paranoia_Config);
  InterfaceOptionsFrame_OpenToCategory(PARANOIA_TABGENERAL);
end

--===================================================================================================================--
--  10) Dropdown List Functions
--===================================================================================================================--

function Paranoia:ShowEDD(eName, bName)
  if (string.find(eName, "emptyButton") ~= nil) then 
    Paranoia:ShowConfig();
    return; 
  end
  channelid, channelname = GetChannelName(PARANOIA_LOCALDEFCHAN.." - "..GetZoneText())

  Dewdrop:Open(Paranoia_Main,
    "children", function()
      Dewdrop:AddLine("text", tostring(eName), "isTitle", true)
      Dewdrop:AddLine("text", PARANOIA_ANNOUNCESAY, "func", function() Paranoia:AnnounceHostile(eName, "SAY"); end)
      Dewdrop:AddLine("text", PARANOIA_ANNOUNCEYELL, "func", function() Paranoia:AnnounceHostile(eName, "YELL"); end)
      if (GetNumPartyMembers() ~= 0) then Dewdrop:AddLine("text", PARANOIA_ANNOUNCEPARTY, "func", function() Paranoia:AnnounceHostile(eName, "PARTY"); end); end
      if (IsInGuild() ~= nil) then Dewdrop:AddLine("text", PARANOIA_ANNOUNCEGUILD, "func", function() Paranoia:AnnounceHostile(eName, "GUILD"); end); end
      if (GetNumRaidMembers() ~= 0 and not Paranoia:IsPlayerInBattlefield()) then Dewdrop:AddLine("text", PARANOIA_ANNOUNCERAID, "func", function() Paranoia:AnnounceHostile(eName, "RAID"); end); end
      if (Paranoia:IsPlayerInBattlefield()) then Dewdrop:AddLine("text", PARANOIA_ANNOUNCEBG, "func", function() Paranoia:AnnounceHostile(eName, "BATTLEGROUND"); end); end
      if (channelid ~= nil and channelid ~= 0) then Dewdrop:AddLine("text", string.format(PARANOIA_ANNOUNCELOCALDEF, channelid), "func", function() Paranoia:AnnounceHostile(eName, "CHANNEL"); end); end
      if (debugEnabled) then Dewdrop:AddLine("text", "|cFFFF7D0ApDebug: Print test announce to chat frame|r", "func", function() Paranoia:AnnounceHostile(eName, "DEBUG"); end); end
      if not Paranoia:IsPlayerInFFA() then
        Dewdrop:AddSeparator();
        Dewdrop:AddLine("text", string.format(PARANOIA_ANNOUNCEIGNORE, eName), "func", function() Dewdrop:Close(); Paranoia:AddToIgnore(eName); end)
      end
      Dewdrop:AddSeparator();
      Dewdrop:AddLine("text", PARANOIA_ANNOUNCECONFIG, "func", function() Paranoia:ShowConfig(); Dewdrop:Close(); end)
    end);
end

function Paranoia:AddToIgnore(name)
  activeHostileList[name] = nil; 
  Paranoia:addNotHostile(name, (60*60*3));
  Paranoia:Msg(string.format(PARANOIA_CHATIGNORED, name)); 
end

function Paranoia:AnnounceHostile(name, channel)
  local classDisp, levelDisp;
  local messageText = Paranoia_Config.customMsg; -- %f = opposite faction, %n = name, %l = level, %c = class, %z = zone, %x and %y = x/y coords respectively
  
  if (channel == "TEST") then
    local posX, posY = GetPlayerMapPosition("player");
    posX=floor(posX *100);
    posY=floor(posY*100);
    messageText = string.gsub(messageText, '%%f', PARANOIA_OPPFACTION);
    messageText = string.gsub(messageText, '%%n', PARANOIA_ANNOUNCE_TESTNAME);
    messageText = string.gsub(messageText, '%%l', "80");
    messageText = string.gsub(messageText, '%%c', PARANOIA_MAGE);
    messageText = string.gsub(messageText, '%%z', GetMinimapZoneText());
    messageText = string.gsub(messageText, '%%x', posX);
    messageText = string.gsub(messageText, '%%y', posY);  
    Paranoia:Msg(PARANOIA_CHATTESTANNOUNCE); 
    DEFAULT_CHAT_FRAME:AddMessage(string.format(PARANOIA_CHATFAKEANNOUNCE, UnitName("player"), messageText)); 
    return;
  end

  if (not activeHostileList[name].class or activeHostileList[name].class == UNKNOWN) then
    classDisp = UNKNOWN;
  else
    classDisp = activeHostileList[name].class;
  end
  
  --replace custom message
  messageText = string.gsub(messageText, '%%f', PARANOIA_OPPFACTION);
  messageText = string.gsub(messageText, '%%n', name);
  messageText = string.gsub(messageText, '%%l', activeHostileList[name].levelDisp);
  messageText = string.gsub(messageText, '%%c', classDisp);
  messageText = string.gsub(messageText, '%%z', activeHostileList[name].zone);
  messageText = string.gsub(messageText, '%%x', activeHostileList[name].x);
  messageText = string.gsub(messageText, '%%y', activeHostileList[name].y);
  
  if (channel == "DEBUG") then
    Paranoia:dMsg("[Paranoia] "..messageText);
    return;
  end
  
  SendChatMessage("[Paranoia] "..messageText, channel, nil, channelid);
  Dewdrop:Close()
end

--===================================================================================================================--
--  11) Minimap Button Functions (using Gello's minimap button template from the WoWI forums)
--===================================================================================================================--

function Paranoia:MinimapButton_Reposition()
	Paranoia_MinimapButton:SetPoint("TOPLEFT","Minimap","TOPLEFT",52-(80*cos(Paranoia_Config.minimapPos)),(80*sin(Paranoia_Config.minimapPos))-52)
  Paranoia:UpdateMinimapVisibility()
end


function Paranoia_MinimapButtonDraggingFrame_OnUpdate()
	local xpos,ypos = GetCursorPosition()
	local xmin,ymin = Minimap:GetLeft(), Minimap:GetBottom()

	xpos = xmin-xpos/UIParent:GetScale()+70 -- get coordinates as differences from the center of the minimap
	ypos = ypos/UIParent:GetScale()-ymin-70
  
	Paranoia_Config.minimapPos = math.deg(math.atan2(ypos,xpos)) -- save the degrees we are relative to the minimap center
	Paranoia:MinimapButton_Reposition() -- move the button
end

-- Put your code that you want on a minimap button click here.  arg1="LeftButton", "RightButton", etc
function Paranoia_MinimapButton_OnClick()
	if arg1 == "LeftButton" then
    if Paranoia_Config.enabled == false then
      Paranoia_Config.enabled = true;
      Paranoia:OnUpdate();
      Paranoia:Msg(PARANOIA_CHATENABLED);
    else
      Paranoia_Config.enabled = false;
      Paranoia:Msg(PARANOIA_CHATDISABLED);
    end
  else
    Paranoia:ShowConfig();
  end
end

function Paranoia:UpdateMinimapVisibility()
  if Paranoia_Config.hideMinimap then
    Paranoia_MinimapButton:Hide();
  else
    Paranoia_MinimapButton:Show();
  end
end